package com.sbc.web;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.sbc.convention.errorcode.BaseErrorCodeEnum;
import com.sbc.convention.exception.AbstractException;
import com.sbc.convention.result.Result;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;

import java.util.Optional;

/**
 * @description: 全局异常处理器，负责处理所有控制器中的异常情况，确保输出一致的响应格式和记录异常日志。
 * @author: songbaicheng
 * @create: 2024-03-20 16:02
 **/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 请求体拦截参数验证异常，处理MethodArgumentNotValidException异常，该异常由Spring MVC在参数校验失败时抛出。
     * 获取第一个验证失败的字段及其错误信息，记录到日志中，并返回一个包含错误码和错误信息的错误响应结果。
     */
    @SneakyThrows
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result<Void> validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());
        String exceptionStr = Optional.ofNullable(firstFieldError)
                .map(FieldError::getDefaultMessage)
                .orElse(StrUtil.EMPTY);
        log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr);
        return Results.failure(BaseErrorCodeEnum.CLIENT_ERROR.code(), exceptionStr);
    }

    /**
     * 请求变量拦截参数验证异常，处理MethodArgumentNotValidException异常，该异常由Spring MVC在参数校验失败时抛出。
     * 获取第一个验证失败的字段及其错误信息，记录到日志中，并返回一个包含错误码和错误信息的错误响应结果。
     */
    @SneakyThrows
    @ExceptionHandler(HandlerMethodValidationException.class)
    public Result<Void> handleHandlerMethodValidationException(HttpServletRequest request, HandlerMethodValidationException ex) {

        String exceptionStr = ex.getMessage();
        log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr);
        return Results.failure(BaseErrorCodeEnum.CLIENT_ERROR.code(), exceptionStr);
    }

    /**
     * 拦截应用内抛出的自定义异常，处理所有继承自AbstractException的异常。
     * 记录异常详细信息到日志中，并返回一个包含异常信息的错误响应结果。
     * 若异常包含cause信息，同时记录cause信息。
     */
    @SneakyThrows
    @ExceptionHandler(value = {AbstractException.class})
    public Result<Void> abstractException(HttpServletRequest request, AbstractException ex) {
        if (ex.getCause() != null) {
            log.error("[{}] {} [ex] - Cause: {}", request.getMethod(), request.getRequestURL().toString(), ex, ex.getCause());
            return Results.failure(ex.getErrorCode(), ex.getMessage());
        }
        log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString());
        return Results.failure(ex.getErrorCode(), ex.getMessage());
    }

    /**
     * 拦截未捕获的异常，处理所有未被前两个方法处理的Throwable异常。
     * 记录异常详细信息到日志中，并返回一个默认的错误响应结果。
     */
    @SneakyThrows
    @ExceptionHandler(value = Throwable.class)
    public Result<Void> defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
        log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
        return Results.failure();
    }

    /**
     * 获取完整的请求URL，包括查询字符串部分。
     */
    private String getUrl(HttpServletRequest request) {
        if (StringUtils.hasLength(request.getQueryString())) {
            return request.getRequestURL().toString();
        }
        return request.getRequestURL().toString() + "?" + request.getQueryString();
    }
}